module Lacrima

  # +Lacrima::Versioning+ standardises and makes it much easier to provide rich
  # metadata about your package's version. Some attributes are guessed if they
  # are not given, such as the name of your package. The common interface to
  # your versioning would look something like this:
  #
  #   module Package
  #     module Version
  #
  #       MAJOR       = 1
  #       MINOR       = 2
  #       TINY        = 3
  #       REVISION    = "$LastChangedRevision: 4 $".match(/LastChangedRevision: (\d+)/)[1]
  #       STRING      = [MAJOR, MINOR, TINY].join('.')
  #       FULL        = [MAJOR, MINOR, TINY, REVISION].join('.')
  #       VERBOSE     = "#{STRING} (r#{REVISION})"
  #       TAG         = "REL_" << [MAJOR, MINOR, TINY].join('_')
  #       NAME        = self.name.split("::")[0..-2] * "::"
  #       URL         = "http://www.example.com/"
  #       SUMMARY     = "A really spectacular package"
  #       DESCRIPTION = "#{NAME} #{VERBOSE}\n#{URL}\n#{SUMMARY}\n"
  #
  #     class << self
  #
  #       attr_acccessor :string, :keyword, :major, :minor, :tiny, :revision
  #       attr_accessor :package_name, :url, :summary
  #
  #       def to_s
  #         "#{NAME} #{VERBOSE}"
  #       end
  #       def [](key)
  #         respond_to?(key) ? send(key) : (const_get(key.to_s.upcase) rescue nil)
  #       end
  #
  #     end
  #   end
  #
  # This rich interface allows you to access any number of attributes as
  # a hash, a method call or a constant.
  #
  # All of this metadata is boiled down into this simple four-line declaration:
  #
  # Lacrima::Versioning.for :Package do
  #   current_version   "1.2.3"
  #   revision_keyword  "$LastChangedRevision: 0 $"
  # end
  #
  # Because it is so short, you can use automated tasks to update your version; for example,
  # just before capistrano deployment or touching other files. In fact, you can do a number
  # of things with it, but most importantly, you can also just leave it alone.
  #
  module Versioning

    class << self
      
      def for(package, &block)
        package = package.name if !package.is_a?(String) && !package.is_a?(Symbol)
        parent = find_or_create_module(package)
        return if parent.const_defined?("Version")
        create_version_for(parent, &block)
      end

      private

      def create_version_for(parent, &block)
        mod = fresh_version_for(parent)
        mod.module_eval(&block)
        assign_numerical_constants(mod)
        assign_string_constants(mod)
        compose_description(mod)
      end
      
      def find_or_create_module(mod, parent = Object)
        parent.const_get(mod)
      rescue
        parent.const_set(mod, Module.new)
      end

      def fresh_version_for(parent)
        child = parent.const_set(:Version, Module.new)
        child.extend Lacrima::Versioning::Definition
      end

      def assign_string_constants(mod)
        %w(package_name summary url string).each do |key|
          mod.const_set key.upcase, mod.send(key)
        end
        mod.const_set   :NAME,      mod.package_name
        mod.const_set   :FULL,      mod.string + (mod.revision ? ".#{mod.revision}" : "")
        mod.const_set   :VERBOSE,   mod.string + (mod.revision ? " (r#{mod.revision})" : "")
        mod.const_set   :TAG,       "REL_#{[mod.major, mod.minor, mod.tiny].join('_')}"
      end

      def assign_numerical_constants(mod)
        %w(major minor tiny revision).each do |key|
          mod.const_set key.upcase, mod.send(key)
        end
      end
      
      def compose_description(mod)
        desc = "#{mod::NAME} #{mod::VERBOSE}"
        desc << "\n#{mod.url}\n" if mod.url
        desc << "#{mod.summary}\n" if mod.summary
        mod.const_set   :DESCRIPTION, desc
      end

    end

    module Definition

      attr_accessor :package_name, :summary, :url, :string, :keyword
      attr_accessor :major, :minor, :tiny, :revision

      def string
        @string || "0.0.1"
      end

      def package_name
        @package_name || name.split("::")[0..-2] * "::"
      end

      def current_version(string)
        self.string = string
        self.major, self.minor, self.tiny = string.split(".").map { |i| i.to_i }
      end

      def revision_keyword(keyword)
        self.keyword = keyword
        self.revision = (keyword ? keyword.match(/^\$\w+:\s+(\d+)\s+\$$/)[1].to_i : nil)
      end
      
      def [](key)
        respond_to?(key) ? send(key) : (const_get(key.to_s.upcase) rescue nil)
      end
      
      def to_s
        "#{const_get(:NAME)} #{const_get(:VERBOSE)}"
      end

    end

  end
end
